// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/filters/pipeline_controller.h"

#include <memory>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "media/base/mock_filters.h"
#include "media/base/pipeline.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::DoAll;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;

namespace media {

class PipelineControllerTest : public ::testing::Test, public Pipeline::Client {
public:
    PipelineControllerTest()
        : pipeline_controller_(&pipeline_,
            base::Bind(&PipelineControllerTest::CreateRenderer,
                base::Unretained(this)),
            base::Bind(&PipelineControllerTest::OnSeeked,
                base::Unretained(this)),
            base::Bind(&PipelineControllerTest::OnSuspended,
                base::Unretained(this)),
            base::Bind(&PipelineControllerTest::OnBeforeResume,
                base::Unretained(this)),
            base::Bind(&PipelineControllerTest::OnResumed,
                base::Unretained(this)),
            base::Bind(&PipelineControllerTest::OnError,
                base::Unretained(this)))
    {
    }

    ~PipelineControllerTest() override { }

    PipelineStatusCB StartPipeline(bool is_streaming, bool is_static)
    {
        EXPECT_FALSE(pipeline_controller_.IsStable());
        PipelineStatusCB start_cb;
        EXPECT_CALL(pipeline_, Start(_, _, _, _)).WillOnce(SaveArg<3>(&start_cb));
        pipeline_controller_.Start(&demuxer_, this, is_streaming, is_static);
        Mock::VerifyAndClear(&pipeline_);
        EXPECT_FALSE(pipeline_controller_.IsStable());
        return start_cb;
    }

    PipelineStatusCB StartPipeline() { return StartPipeline(false, true); }

    PipelineStatusCB StartPipeline_WithDynamicData()
    {
        return StartPipeline(false, false);
    }

    PipelineStatusCB StartPipeline_WithStreamingData()
    {
        return StartPipeline(true, false);
    }

    PipelineStatusCB SeekPipeline(base::TimeDelta time)
    {
        EXPECT_TRUE(pipeline_controller_.IsStable());
        PipelineStatusCB seek_cb;
        EXPECT_CALL(pipeline_, Seek(time, _)).WillOnce(SaveArg<1>(&seek_cb));
        pipeline_controller_.Seek(time, true);
        Mock::VerifyAndClear(&pipeline_);
        EXPECT_FALSE(pipeline_controller_.IsStable());
        return seek_cb;
    }

    PipelineStatusCB SuspendPipeline()
    {
        EXPECT_TRUE(pipeline_controller_.IsStable());
        PipelineStatusCB suspend_cb;
        EXPECT_CALL(pipeline_, Suspend(_)).WillOnce(SaveArg<0>(&suspend_cb));
        pipeline_controller_.Suspend();
        Mock::VerifyAndClear(&pipeline_);
        EXPECT_TRUE(pipeline_controller_.IsSuspended());
        EXPECT_FALSE(pipeline_controller_.IsStable());
        EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended());
        return suspend_cb;
    }

    PipelineStatusCB ResumePipeline()
    {
        EXPECT_TRUE(pipeline_controller_.IsPipelineSuspended());
        PipelineStatusCB resume_cb;
        EXPECT_CALL(pipeline_, Resume(_, _, _))
            .WillOnce(
                DoAll(SaveArg<1>(&last_resume_time_), SaveArg<2>(&resume_cb)));
        EXPECT_CALL(pipeline_, GetMediaTime())
            .WillRepeatedly(Return(base::TimeDelta()));
        pipeline_controller_.Resume();
        Mock::VerifyAndClear(&pipeline_);
        EXPECT_FALSE(pipeline_controller_.IsSuspended());
        EXPECT_FALSE(pipeline_controller_.IsStable());
        EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended());
        return resume_cb;
    }

    void Complete(const PipelineStatusCB& cb)
    {
        cb.Run(PIPELINE_OK);
        base::RunLoop().RunUntilIdle();
    }

protected:
    std::unique_ptr<Renderer> CreateRenderer()
    {
        return std::unique_ptr<Renderer>();
    }

    void OnSeeked(bool time_updated)
    {
        was_seeked_ = true;
        last_seeked_time_updated_ = time_updated;
    }

    void OnSuspended() { was_suspended_ = true; }
    void OnBeforeResume() { was_resuming_ = true; }
    void OnResumed() { was_resumed_ = true; }

    // Pipeline::Client overrides
    void OnError(PipelineStatus status) override { NOTREACHED(); }
    void OnEnded() override { }
    void OnMetadata(PipelineMetadata metadata) override { }
    void OnBufferingStateChange(BufferingState state) override { }
    void OnDurationChange() override { }
    void OnAddTextTrack(const TextTrackConfig& config,
        const AddTextTrackDoneCB& done_cb) override { }
    void OnWaitingForDecryptionKey() override { }
    void OnVideoNaturalSizeChange(const gfx::Size& size) override { }
    void OnVideoOpacityChange(bool opaque) override { }
    void OnVideoAverageKeyframeDistanceUpdate() override { }

    base::MessageLoop message_loop_;

    NiceMock<MockDemuxer> demuxer_;
    StrictMock<MockPipeline> pipeline_;
    PipelineController pipeline_controller_;

    bool was_seeked_ = false;
    bool last_seeked_time_updated_ = false;
    bool was_suspended_ = false;
    bool was_resuming_ = false;
    bool was_resumed_ = false;
    base::TimeDelta last_resume_time_;

    DISALLOW_COPY_AND_ASSIGN(PipelineControllerTest);
};

TEST_F(PipelineControllerTest, Startup)
{
    PipelineStatusCB start_cb = StartPipeline();
    EXPECT_FALSE(was_seeked_);

    Complete(start_cb);
    EXPECT_TRUE(was_seeked_);
    EXPECT_FALSE(last_seeked_time_updated_);
    EXPECT_FALSE(was_suspended_);
    EXPECT_TRUE(pipeline_controller_.IsStable());
}

TEST_F(PipelineControllerTest, SuspendResume)
{
    Complete(StartPipeline());
    EXPECT_TRUE(was_seeked_);
    was_seeked_ = false;

    Complete(SuspendPipeline());
    EXPECT_TRUE(was_suspended_);
    EXPECT_FALSE(pipeline_controller_.IsStable());

    PipelineStatusCB resume_cb = ResumePipeline();
    EXPECT_TRUE(was_resuming_);
    EXPECT_FALSE(was_resumed_);

    Complete(resume_cb);
    EXPECT_TRUE(was_resumed_);
    EXPECT_TRUE(pipeline_controller_.IsStable());

    // |was_seeked_| should not be affected by Suspend()/Resume() at all.
    EXPECT_FALSE(was_seeked_);
}

TEST_F(PipelineControllerTest, Seek)
{
    // Normal seeking should not result in a cancel.
    EXPECT_CALL(demuxer_, CancelPendingSeek(_)).Times(0);

    Complete(StartPipeline());
    was_seeked_ = false;

    base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
    EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time));
    PipelineStatusCB seek_cb = SeekPipeline(seek_time);
    base::RunLoop().RunUntilIdle();
    EXPECT_FALSE(was_seeked_);

    Complete(seek_cb);
    EXPECT_TRUE(was_seeked_);
    EXPECT_TRUE(pipeline_controller_.IsStable());
}

TEST_F(PipelineControllerTest, SuspendResumeTime)
{
    Complete(StartPipeline());
    Complete(SuspendPipeline());

    base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
    pipeline_controller_.Seek(seek_time, true);
    base::RunLoop().RunUntilIdle();

    Complete(ResumePipeline());
    EXPECT_EQ(seek_time, last_resume_time_);
}

TEST_F(PipelineControllerTest, SuspendResumeTime_WithStreamingData)
{
    Complete(StartPipeline_WithStreamingData());
    Complete(SuspendPipeline());

    base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
    pipeline_controller_.Seek(seek_time, true);
    base::RunLoop().RunUntilIdle();

    Complete(ResumePipeline());
    EXPECT_EQ(base::TimeDelta(), last_resume_time_);
}

TEST_F(PipelineControllerTest, SeekAborted)
{
    Complete(StartPipeline());

    // Create a first pending seek.
    base::TimeDelta seek_time_1 = base::TimeDelta::FromSeconds(5);
    EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time_1));
    PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time_1);
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClear(&demuxer_);

    // Create a second seek; the first should be aborted.
    base::TimeDelta seek_time_2 = base::TimeDelta::FromSeconds(10);
    EXPECT_CALL(demuxer_, CancelPendingSeek(seek_time_2));
    pipeline_controller_.Seek(seek_time_2, true);
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClear(&demuxer_);

    // When the first seek is completed (or aborted) the second should be issued.
    EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time_2));
    EXPECT_CALL(pipeline_, Seek(seek_time_2, _));
    Complete(seek_cb_1);
}

TEST_F(PipelineControllerTest, PendingSuspend)
{
    Complete(StartPipeline());

    base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
    PipelineStatusCB seek_cb = SeekPipeline(seek_time);
    base::RunLoop().RunUntilIdle();

    // While the seek is ongoing, request a suspend.
    // It will be a mock failure if pipeline_.Suspend() is called.
    pipeline_controller_.Suspend();
    base::RunLoop().RunUntilIdle();

    // Expect the suspend to trigger when the seek is completed.
    EXPECT_CALL(pipeline_, Suspend(_));
    Complete(seek_cb);
}

TEST_F(PipelineControllerTest, SeekMergesWithResume)
{
    Complete(StartPipeline());
    Complete(SuspendPipeline());

    // Request a seek while suspended.
    // It will be a mock failure if pipeline_.Seek() is called.
    base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
    pipeline_controller_.Seek(seek_time, true);
    base::RunLoop().RunUntilIdle();

    // Resume and verify the resume time includes the seek.
    Complete(ResumePipeline());
    EXPECT_EQ(seek_time, last_resume_time_);
    EXPECT_TRUE(last_seeked_time_updated_);
}

TEST_F(PipelineControllerTest, SeekMergesWithSeek)
{
    Complete(StartPipeline());

    base::TimeDelta seek_time_1 = base::TimeDelta::FromSeconds(5);
    PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time_1);
    base::RunLoop().RunUntilIdle();

    // Request another seek while the first is ongoing.
    base::TimeDelta seek_time_2 = base::TimeDelta::FromSeconds(10);
    pipeline_controller_.Seek(seek_time_2, true);
    base::RunLoop().RunUntilIdle();

    // Request a third seek. (It should replace the second.)
    base::TimeDelta seek_time_3 = base::TimeDelta::FromSeconds(15);
    pipeline_controller_.Seek(seek_time_3, true);
    base::RunLoop().RunUntilIdle();

    // Expect the third seek to trigger when the first seek completes.
    EXPECT_CALL(pipeline_, Seek(seek_time_3, _));
    Complete(seek_cb_1);
}

TEST_F(PipelineControllerTest, SeekToSeekTimeElided)
{
    Complete(StartPipeline());

    base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
    PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time);
    base::RunLoop().RunUntilIdle();

    // Request a seek to the same time again.
    pipeline_controller_.Seek(seek_time, true);
    base::RunLoop().RunUntilIdle();

    // Complete the first seek.
    // It would be a mock error if the second seek was dispatched here.
    Complete(seek_cb_1);
    EXPECT_TRUE(pipeline_controller_.IsStable());
}

TEST_F(PipelineControllerTest, SeekToSeekTimeNotElided)
{
    Complete(StartPipeline_WithDynamicData());

    base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
    PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time);
    base::RunLoop().RunUntilIdle();

    // Request a seek to the same time again.
    pipeline_controller_.Seek(seek_time, true);
    base::RunLoop().RunUntilIdle();

    // Expect the second seek to trigger when the first seek completes.
    EXPECT_CALL(pipeline_, Seek(seek_time, _));
    Complete(seek_cb_1);
}

} // namespace media
